//           Copyright Daniel Goertzen 2007.
//  Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)

#ifndef XCODE_HPP
#define XCODE_HPP

#include <boost/numeric/conversion/converter.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits.hpp>

#include <boost/typeof/typeof.hpp>
#define auto BOOST_AUTO

#include "endian.hpp"

namespace eipp
{

	using namespace boost;
	using namespace boost::integer;

#define ERL_VERSION_MAGIC (131)
#define ERL_SMALL_INTEGER_EXT (97)
#define ERL_INTEGER_EXT       (98)
#define ERL_FLOAT_EXT_R0         (99)
#define ERL_ATOM_EXT          (100)
#define ERL_REFERENCE_EXT     (101)
#define ERL_PORT_EXT          (102)
#define ERL_PID_EXT           (103)
#define ERL_SMALL_TUPLE_EXT   (104)
#define ERL_LARGE_TUPLE_EXT   (105)
#define ERL_NIL_EXT           (106)
#define ERL_STRING_EXT        (107)
#define ERL_LIST_EXT          (108)
#define ERL_BINARY_EXT        (109)
#define ERL_SMALL_BIG_EXT     (110)
#define ERL_LARGE_BIG_EXT     (111)
#define ERL_NEW_CACHE         (78)
#define ERL_CACHED_ATOM       (67)
#define ERL_NEW_REFERENCE_EXT		(114)
#define ERL_FUN_EXT		(117)
#define ERL_NEW_FUN_EXT	(112)
#define ERL_EXPORT_EXT	(113)
#define ERL_BIT_BINARY_EXT	(77)
#define ERL_FLOAT_EXT_R1         (70)

//#define COMPRESSED (80)




///////////////////////
// build_lut
//
// Build an addr->size lookup table for the given term so that the
// sizes of contained elements only needs to be scanned through this
// one time.  The address is simply the start of the term (type
// identifier byte), and the size is how many bytes need to be skipped
// to get to the next term.  Container elements (list and tuple) skip
// past ALL contained terms (even if they are containers).
//
// 

	typedef map<const uint8_t *,size_t> lut_t;
//typedef shared_ptr<map<int8_t *,size_t> > const_lut_t;

	uint8_t *build_lut_main(lut_t &lut, const uint8_t *cur, const uint8_t *end)
	{
		//FIXME: need to actually bomb out if we go past the end

		size_t size=0;
		switch(*cur)
		{
		case ERL_SMALL_INTEGER_EXT:	size=2;	 break;
		case ERL_INTEGER_EXT:       size=5;  break;
		case ERL_FLOAT_EXT_R0:      size=32; break;
		case ERL_NIL_EXT:           size=1;  break;
		case ERL_CACHED_ATOM:       size=2;  break;

		case ERL_PORT_EXT:
		case ERL_REFERENCE_EXT:
		{
			const uint8_t* nodeend=build_lut_main(lut,cur+1,end);
			size=(nodeend-cur)+5;
			break;
		}

		case ERL_NEW_REFERENCE_EXT:
		{
			int Np_size=(*(ubig16_t *)(cur+1));
			Np_size*=4;
			const uint8_t* nodeend=build_lut_main(lut,cur+3,end);
			size=(nodeend-cur)+1+Np_size;
			break;
		}

		case ERL_PID_EXT:
		{
			const uint8_t* nodeend=build_lut_main(lut,cur+1,end);
			size=(nodeend-cur)+9;
			break;
		}

		case ERL_SMALL_TUPLE_EXT:
		{
			int count=*(ubig8_t *)(cur+1);
			const uint8_t *newend=cur+2; // in case of empty tuple
			for(int i=0;i<count;i++)
			{
				newend=build_lut_main(lut,newend,end);
			}
			size=newend-cur;
			break;
		}

		case ERL_LARGE_TUPLE_EXT:
		{
			int count=*(ubig32_t *)(cur+1);
			const uint8_t *newend=cur+5; // in case of empty tuple
			for(int i=0;i<count;i++)
			{
				newend=build_lut_main(lut,newend,end);
			}
			size=newend-cur;
			break;
		}

		case ERL_ATOM_EXT:
		case ERL_STRING_EXT:
		{
			int count=*(ubig16_t *)(cur+1);
			size=count+3;
			break;
		}

		case ERL_LIST_EXT:
		{
			int count=*(ubig32_t *)(cur+1);
			count++; // include the tail element

			const uint8_t *newend=cur+5; // in case of empty tuple
			for(int i=0;i<count;i++)
			{
				newend=build_lut_main(lut,newend,end);
			}
			size=newend-cur;
			break;
		}

		case ERL_NEW_FUN_EXT:
		case ERL_BINARY_EXT:
		{
			int count=*(ubig32_t *)(cur+1);
			size=count+5;
			break;
		}

		case ERL_SMALL_BIG_EXT:
		{
			int count=*(ubig8_t *)(cur+1);
			size=count+3;
			break;
		}

		case ERL_LARGE_BIG_EXT:
		{
			int count=*(ubig32_t *)(cur+1);
			size=count+6;
			break;
		}

		case ERL_FUN_EXT:
		{
			int freesize=(*(ubig16_t *)(cur+1));
			freesize*=4;
			const uint8_t* newend=cur+5;
			newend=build_lut_main(lut,newend,end);
			newend=build_lut_main(lut,newend,end);
			newend=build_lut_main(lut,newend,end);
			newend=build_lut_main(lut,newend,end);
			const uint8_t* nodeend=build_lut_main(lut,cur+3,end);
			size=(newend-cur)+freesize;
			break;
		}

		case ERL_NEW_CACHE:
		{
			int count=*(ubig16_t *)(cur+2);
			size=count+4;
			break;
		}
		default:
			throw bad_match("unknown encoding");
		} // switch
	}// function

	void build_lut(lut_t &lut, const uint8_t *cur, const uint8_t *end)
	{
		if(*cur==131)cur++; // skip version magic
		build_lut_main(lut,cur,end);
	}





	struct encoded_t
	{
		encoded_t(const et &_bin)
			:bin(_bin)
			{
//				const binary_t &v((const binary_t &)bin);
				const binary_t &v(bin);
				cur=&v[0];

				shared_ptr<lut_t> mlut;
				build_lut(*mlut,&v[0],&v[v.size()]);
				lut=mlut;
			}

		encoded_t(const encoded_t &old, const uint8_t *newcur)
			:bin(old.bin),lut(old.lut),cur(newcur)
			{}

		const et bin; //FIXME should constify this somehow
		shared_ptr<const lut_t> lut;
		const uint8_t *cur;
	};


/////////////////////////////
//
// decode helpers for numerical types
//

// template<typename T>
// decode_big(T &v, const uint8_t *buf, uint32_t size, bool negative, is_float, is_int)

//decode as float
	template<typename T>
	void decode_big(T &v, const uint8_t *buf, uint32_t size, bool negative,
					const boost::true_type, //is_floating_point
					const boost::false_type) //is_integral
	{
		// should we check for overflows??

		T temp=0;
		while(size--)
		{
			temp=temp*256.0 + ((uint8_t *)buf)[size];
		}

		// A possible optimization to the above algorithm would be to only
		// process the first X bytes since the float type has limited
		// significance.

		if(negative)
		{
			v=-temp;
		}
		else
		{
			v=temp;
		}
	}


//decode as unsigned int
	template<typename T>
	void decode_big_to_int(T &v, const uint8_t *buf, uint32_t size, bool negative,
						   const boost::false_type) //is_signed
	{
		if(size > sizeof(T)) throw bad_match("can't decode, integer overflow");
		if(negative) throw bad_match("can't decode, integer underflow");

		// this decode may not be efficient, but should be endian safe
		T temp=0;
		while(size--)
		{
			temp=temp<<8 + ((uint8_t *)buf)[size];
		}
		v=temp;
	}

//decode as signed int
	template<typename T>
	void decode_big_to_int(T &v, const uint8_t *buf, uint32_t size, bool negative,
						   const boost::true_type) //is_signed
	{
		if(size > sizeof(T)) throw bad_match("can't decode, integer too narrow");

		// this decode may not be efficient, but should be endian safe
		T temp=0;
		while(size--)
		{
			temp=temp<<8 + ((uint8_t *)buf)[size];
		}

		if(negative)
		{
			v=-temp;
			if(temp>0) throw bad_match("can't decode, integer underflow");
		}
		else
		{
			v=temp;
			if(temp<0) throw bad_match("can't decode, integer overflow");
		}
	}

	template<typename T>
	void decode_big(T &v, const uint8_t *buf, uint32_t size, bool negative,
					const boost::false_type, //is_floating_point
					const boost::true_type) //is_integral
	{
		decode_big_to_int(v,buf,size,negative,is_signed<T>());
	}
	

	template<typename T>
	void decode_big(T &v, const uint8_t *buf, uint32_t size, bool negative)
	{
		decode_big(v,buf,size,negative,is_floating_point<T>(),is_integral<T>());
	}


// specialized decoders
	void decode(binary_t &v,const encoded_t &enc, const uint8_t *buf)
	{
		if((*buf)!=ERL_BINARY_EXT) throw bad_match("not a binary");
		size_t size=*(ubig32_t*)(buf+1);
		v.clear();
		v.insert(v.begin(),buf+5,buf+5+size);
	}

	void decode(tuple_t &v,const encoded_t &enc, const uint8_t *buf)
	{
		uint8_t type=*buf++;

		uint32_t arity;

		const lut_t &lut(*(enc.lut));

		if(type==ERL_SMALL_TUPLE_EXT)
		{
			arity=*(ubig8_t *)(buf);
			buf+=1;
		}
		else if(type==ERL_LARGE_TUPLE_EXT)
		{
			arity=*(ubig32_t *)(buf);
			buf+=4;
		}

		v.clear();
		for(uint32_t i=0;i<arity;i++)
		{
			auto(j,lut.find(buf));

			if(j==lut.end())
			{
				throw bad_match("encoded tuple looks corrupt");
			}
			v.push_back( et(encoded_t(enc,buf)) );
			buf+=j->second;
		}
	}

	void decode(list_t &v,const encoded_t &enc, const uint8_t *buf)
	{
		uint32_t arity;
		const lut_t &lut(*(enc.lut));

		v.clear();
		while(1)
		{
			uint8_t type=*buf++;
			if(type==ERL_NIL_EXT)
			{
				return;
			}
			else if(type==ERL_LIST_EXT)
			{
				arity=*(ubig32_t *)(buf);
				buf+=4;
			}
			else // improper list (tail of last cons is not NIL)
			{
				
				if(v.size()==0) // hmmm, this isn't a list at all then...
					throw bad_match("not a list");

				v.push_back( encoded_t(enc,buf) );
				return;
			}
	

			// process cons head
			for(uint32_t i=0;i<arity;i++)
			{
				auto(j,lut.find(buf));
				if(j==lut.end())
				{
					throw bad_match("encoded tuple looks corrupt");
				}
				v.push_back( encoded_t(enc,buf) );
				buf+=j->second;
			}
			
			// loop to process tail
		}
	}

	void decode(atom_t &v,const encoded_t &enc, const uint8_t *buf)
	{
		uint8_t type=*buf++;
		if(type!=ERL_ATOM_EXT) throw bad_match("not an atom");

		int count=*(ubig16_t *)buf;
		buf+=2;
		v.clear();
		v.insert(v.begin(),buf,buf+count);
	}

	void decode(string_t &v,const encoded_t &enc, const uint8_t *buf)
	{
		v.clear();

		uint8_t type=*buf;

		// is this encoded as a string?
		if(type==ERL_STRING_EXT)
		{
			int count=*(ubig16_t *)buf;
			buf+=2;
			v.clear();
			v.insert(v.begin(),buf,buf+count);
			return;
		}

		// or is it encoded as a list?

		while(1)
		{
			type=*buf++;
			if(type==ERL_LIST_EXT)
			{
				uint32_t arity=*(ubig32_t *)(buf);
				buf+=4;
			
				for(uint32_t i=0;i<arity;i++)
				{
					type=*buf++;
					if(type!=ERL_SMALL_INTEGER_EXT)
						throw bad_match("bad list-encoded string");
					v.push_back(*buf++);
				}
			}
			else if(type==ERL_NIL_EXT)
			{
				return;
			}
			else if(type==ERL_SMALL_INTEGER_EXT) // final tail of improper list
			{
				if(v.size()==0) // not a list
					throw bad_match("not a string");
				v.push_back(*buf);
				return;
			}
			else
			{
				throw bad_match("not a string");
			}
		}
	}

// All remaining types that we want to support are numeric types
// (unsigned, signed, floating point), so handle conversion with
// template magic.
	template<typename T>
	void decode(T &v,const encoded_t &enc, const uint8_t *buf)
	{
		// hmmm, there's no generic number concept check.. they are int or float specific.
		//BOOST_CLASS_REQUIRE(type, namespace-of-concept, concept);
        // see trait is_arithmetic<>

		uint8_t type=*buf++;

		switch(type)
		{
		case ERL_SMALL_INTEGER_EXT:
		{
			boost::numeric::converter<T,int8_t> conv;
			v=conv.convert(*(int8_t*)buf);
			break;
		}

		case ERL_INTEGER_EXT:
		{
			boost::numeric::converter<T,int32_t> conv;
			v=conv.convert((int32_t)*(big32_t*)buf);
			break;
		}

		case ERL_FLOAT_EXT_R0:
		{
			const std::string dat((const char *)buf,31);
			v=boost::lexical_cast<T>(dat);
			break;
		}

		case ERL_FLOAT_EXT_R1:
		{
			// assume this machine uses IEEE floating point numbers

			// maneuver the data into this double first (for endian conversion)
			double dv;
			int64_t *temp=(int64_t*)&dv;
			*temp=(int64_t)*(big64_t*)buf;

			// proceed with conversion to T
			boost::numeric::converter<T,double> conv;
			v=conv.convert(dv);
			break;
		}

		case ERL_SMALL_BIG_EXT:
		{
			uint32_t arity=(uint32_t)(*buf);
			bool negative=(*(buf+1));
			decode_big(v,buf+2,arity,negative);
			break;
		}

		case ERL_LARGE_BIG_EXT:
		{
			uint32_t arity=*((ubig32_t*)buf);
			bool negative=(*(buf+4));
			decode_big(v,buf+5,arity,negative);
			break;
		}

		default:
			throw bad_match("unknown term type");
		}
	}


}//namespace

#endif
